use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;
use syn::Lit;

use super::ToNodeIterator;
use crate::stringify::Stringify;
use crate::PeekValue;

pub enum HtmlNode {
    Literal(Box<Lit>),
    Expression(Box<TokenStream>),
}

impl Parse for HtmlNode {
    fn parse(input: ParseStream) -> Result<Self> {
        let node = if HtmlNode::peek(input.cursor()).is_some() {
            let lit = input.parse()?;
            match lit {
                Lit::ByteStr(lit) => {
                    return Err(syn::Error::new(
                        lit.span(),
                        "byte-strings can't be converted to HTML text
                         note: remove the `b` prefix or convert this to a `String`",
                    ))
                }
                Lit::Verbatim(lit) => {
                    return Err(syn::Error::new(lit.span(), "unsupported literal"))
                }
                _ => (),
            }
            HtmlNode::Literal(Box::new(lit))
        } else {
            HtmlNode::Expression(Box::new(input.parse()?))
        };

        Ok(node)
    }
}

impl PeekValue<()> for HtmlNode {
    fn peek(cursor: Cursor) -> Option<()> {
        cursor.literal().map(|_| ()).or_else(|| {
            let (ident, _) = cursor.ident()?;
            match ident.to_string().as_str() {
                "true" | "false" => Some(()),
                _ => None,
            }
        })
    }
}

impl ToTokens for HtmlNode {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(match &self {
            HtmlNode::Literal(lit) => {
                let sr = lit.stringify();
                quote_spanned! {lit.span()=> ::yew::virtual_dom::VText::new(#sr) }
            }
            HtmlNode::Expression(expr) => quote! {#expr},
        });
    }
}

impl ToNodeIterator for HtmlNode {
    fn to_node_iterator_stream(&self) -> Option<TokenStream> {
        match self {
            Self::Literal(_) => None,
            Self::Expression(expr) => {
                // NodeSeq turns both Into<T> and Vec<Into<T>> into IntoIterator<Item = T>
                Some(quote_spanned! {expr.span().resolved_at(Span::call_site())=>
                    ::std::convert::Into::<::yew::utils::NodeSeq<_, _>>::into(#expr)
                })
            }
        }
    }

    fn is_singular(&self) -> bool {
        match self {
            Self::Literal(_) => true,
            Self::Expression(_) => false,
        }
    }
}
